/*
  Ejemplo de programa servidor. Atiende peticiones aritmeticas, el protocolo de transporte sera SIP 2.0
  ver ims::sip::Transport y el contenido del mensaje sera el resutaldo de un comm::Codec con los
  (x, y, op) -> El resultado sera estos tres componente mas result.

    Ejemplo de implementacion de un UAS-IMS.
  
   Los clientes pueden ser: ims_kclient.p e ims_client.p (futuro).
*/
#include <iostream>

#include <nemesis.h>
#include <nemesis.comm.h>

#include <nemesis.xml.Node.h>
#include <nemesis.xml.Attribute.h>

#include <nemesis.ims.sip.Transport.h>
#include <nemesis.ims.sip.functions.h>
#include <nemesis.ims.sip.Request.h>
#include <nemesis.ims.sip.Response.h>

#include <nemesis.ims.useragent.ServerAgent.h>
#include <nemesis.ims.useragent.functions.h>
#include <nemesis.ims.useragent.Connection.h>

#include <test.Request.h>
#include <test.Response.h>
#include <test.Communicator.h>

using namespace std;

class MyCommunicator : public test::Communicator {
public:
   MyCommunicator () {;}
};

class UAServer : public ims::useragent::ServerAgent {
public:
   UAServer () : ims::useragent::ServerAgent ("UAServer") {;}

   static const char* className () throw () { return "sip_rserver::ReceiverFactory"; }

private:
   test::Request a_testRequest;
   test::Response a_testResponse;

   void eventNotify (ims::useragent::Connection&, const ims::sip::Request& request, ims::sip::Response& response) 
      throw (RuntimeException);
};

class SIPArithmeticServer : public comm::Application {
public:
   SIPArithmeticServer ();
      
private:
   MyCommunicator a_communicator;
   UAServer a_userAgentServer;
   ReceiverFactoryImpl <UAServer> a_receiverFactory;

   void initialize () throw (RuntimeException);
   void run () throw (RuntimeException);
};

using namespace std;
using namespace nemesis::comm;

int main (int argc, const char** argv)
{
   CommandLine& commandLine (CommandLine::instanciate ());
   SIPArithmeticServer app;

   ims::sip::functions::initialize ();
   ims::useragent::functions::initialize ();

   srand (time (NULL));

   try {
      commandLine.initialize (argv, argc);
      commandLine.verify ();

      Logger::setLevel (Logger::Debug); 
      Logger::initialize ("sip_server", new nemesis::TraceWriter ("file.trace", 4048000));
 
      app.start ();
   }
   catch (Exception& ex) {
      cout << ex.asString () << endl;
   }
   
   return 0;
}

SIPArithmeticServer::SIPArithmeticServer () : 
   Application ("sip_rserver", "Servidor SIP de operaciones aritmeticas (iRS)", "1.0") 
{
   CommandLine& commandLine (CommandLine::instanciate ());
      
   commandLine.add ("p", CommandLine::Argument::Mandatory, "Puerto en el que atender peticiones");
   commandLine.add ("a", CommandLine::Argument::Mandatory, "Direccion IP (broadcast) en la que atender");
   commandLine.add ("trace", CommandLine::Argument::Optional, "Nivel de trazas (debug,warning, error,...)");
   commandLine.add ("udp", CommandLine::Argument::Optional, "Indica que se debe usar una conexion UDP", false);
}

void SIPArithmeticServer::initialize () 
   throw (RuntimeException)
{
   CommandLine& cl (CommandLine::instanciate ());

   int port = cl.getIntegerValue ("p");
   const comm::Device* device = Network::instanciate ().find (Device::asAddress (cl.getValue ("a")));

   if (cl.exists ("udp")) {
      comm::DatagramSocket* datagramSocket;
      datagramSocket = new DatagramSocket (DatagramSocket::ReadOnly, INetAddress (device, port), &ims::sip::Transport::getFactory ());
      datagramSocket->setReceiver (&a_userAgentServer);   
      a_communicator.attach (datagramSocket);
   }
   else {
      comm::ServerSocket* serverSocket;
      serverSocket = new ServerSocket (INetAddress (device, port), false, &ims::sip::Transport::getFactory ());
      serverSocket->setReceiverFactory (a_receiverFactory);   
      a_communicator.attach (serverSocket);
   }
}

void SIPArithmeticServer::run ()
   throw (RuntimeException)
{
   CommandLine& cl (CommandLine::instanciate ());

   if (cl.exists ("trace"))
      Logger::setLevel (Logger::asLevel (cl.getValue ("trace")));

   a_communicator.accept ();
}

void UAServer::eventNotify (ims::useragent::Connection& connection, const ims::sip::Request& request, ims::sip::Response& response)
   throw (RuntimeException)
{
   LOGMETHOD (TraceMethod tm ("UAServer", "apply", FILE_LOCATION));

   const DataBlock& body = request.getBody ();

   if (body.getSize () == 0)
      throw RuntimeException ("La peticion no incorpora los parametros de operacion", FILE_LOCATION);

   LOGINFORMATION (  
      string msg ("Body recibido: ");
      msg += nemesis::functions::asString (body);
      Logger::information (msg, FILE_LOCATION);
   );

   if (Codec::getType (body) != test::Request::Id) 
      throw RuntimeException ("El mensaje recibido no es una peticion aritmetica", FILE_LOCATION);

   a_testRequest.decode (body);
   
   a_testResponse.x = a_testRequest.x;
   a_testResponse.y = a_testRequest.y;
   a_testResponse.initTime = a_testRequest.initTime;
   
   response.setStatusCode (200);

   switch (a_testResponse.op = a_testRequest.op) {
      case '+':
         a_testResponse.result = a_testRequest.x + a_testRequest.y;
         break;
      case '-':
         a_testResponse.result = a_testRequest.x - a_testRequest.y;
         break;
      case '*':
         a_testResponse.result = a_testRequest.x * a_testRequest.y;
         break;
      case '/':
         if (a_testRequest.y == 0) {
            response.setStatusCode (400);
            response.setReasonPhrase ("Division por cero");
            a_testResponse.result = 0;
         }
         else
            a_testResponse.result = a_testRequest.x / a_testRequest.y;
         break;
   }

   response.setBody (a_testResponse.code ());      

   LOGINFORMATION (
      string msg = nemesis::functions::asString ("%d %c %d = %d", a_testRequest.x, a_testRequest.op, a_testRequest.y, a_testResponse.result);
      Logger::information (msg, FILE_LOCATION);
   );

   try {
      connection.send (response);
   }
   catch (Exception& ex) {
      ex.trace ();
   }
}

